<template>
  <div
    v-if="ready"
    class="vue-marquee"
    :class="{ vertical: vertical, horizontal: !vertical }"
    :style="getCurrentStyle"
    :key="componentKey"
    @mouseenter="hoverStarted"
    @mouseleave="hoverEnded"
    @mousedown="mouseDown"
    @mouseup="mouseUp"
  >
    <div class="transparent-overlay" ref="marqueeOverlayContainer" :aria-hidden="true"></div>
    <div
      v-if="showGradient"
      :aria-hidden="true"
      class="overlay"
      :class="{ vertical: vertical, horizontal: !vertical }"
    ></div>
    <div class="marquee" ref="marqueeContent">
      <slot></slot>
    </div>
    <div class="marquee" :aria-hidden="true">
      <slot></slot>
    </div>

    <div :aria-hidden="true" class="marquee cloned" v-for="num in cloneAmount" :key="num">
      <slot></slot>
    </div>
  </div>
</template>

<script>
export default {
  name: 'vue-marquee',
  props: {
    vertical: {
      type: Boolean,
      default: false,
    },

    direction: {
      type: String,
      validator(value) {
        return ['normal', 'reverse'].includes(value);
      },
      default: 'normal',
    },

    duration: {
      type: Number,
      default: 20,
    },

    delay: {
      type: Number,
      default: 0,
    },

    loop: {
      type: Number,
      default: 0,
    },

    clone: {
      type: Boolean,
      default: false,
    },

    gradient: {
      type: Boolean,
      default: false,
    },

    gradientColor: {
      type: Array,
      default: () => [255, 255, 255],
    },

    gradientWidth: {
      type: String,
    },

    gradientLength: {
      type: String,
    },

    pauseOnHover: {
      type: Boolean,
      default: false,
    },

    pauseOnClick: {
      type: Boolean,
      default: false,
    },

    pause: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      cloneAmount: 0,

      minWidth: '100%',
      minHeight: '100%',

      componentKey: 0,

      verticalAnimationPause: false,

      containerWidth: 0,
      contentWidth: 0,

      containerHeight: 0,
      contentHeight: 0,

      loopCounter: 0,
      loopInterval: null,

      currentGradientLength: '200px',

      ready: false,

      marqueeContent: null,
      marqueeOverlayContainer: null,
    };
  },
  computed: {
    getCurrentStyle() {
      const cssVariables = {
        '--duration': `${this.duration}s`,
        '--delay': `${this.delay}s`,
        '--direction': `${this.direction}`,
        '--pauseOnHover': `${this.pauseOnHover ? 'paused' : 'running'}`,
        '--pauseOnClick': `${this.pauseOnClick ? 'paused' : 'running'}`,
        '--pauseAnimation': `${(this.vertical && this.verticalAnimationPause) || this.pause ? 'paused' : 'running'}`,
        '--loops': `${this.loop === 0 ? 'infinite' : this.loop}`,
        '--gradient-color': `rgba(${this.gradientColor[0]}, ${this.gradientColor[1]}, ${this.gradientColor[2]}, 1), rgba(${this.gradientColor[0]}, ${this.gradientColor[1]}, ${this.gradientColor[2]}, 0)`,
        '--gradient-length': `${this.currentGradientLength}`,
        '--min-width': `${this.minWidth}`,
        '--min-height': `${this.minHeight}`,
      };

      const animationStyles = {
        '--orientation': 'vue-marquee-scrollX',
        orientation: 'horizontal',
      };

      if (this.vertical) {
        animationStyles['--orientation'] = 'vue-marquee-scrollY';
      }

      const styles = {
        ...cssVariables,
        ...animationStyles,
      };

      return styles;
    },

    showGradient() {
      if (this.gradient) {
        return true;
      }
      return false;
    },
  },
  watch: {
    contentWidth() {
      if (this.clone) {
        this.ForcesUpdate();
      }
    },

    containerWidth() {
      if (this.clone) {
        this.ForcesUpdate();
      }
    },

    // watch pauseAnimation for emitting events
    pause(newVal, oldVal) {
      if (newVal !== oldVal) {
        if (newVal) {
          this.$emit('onResume');
        } else {
          this.$emit('onPause');
        }
      }
    },
  },
  mounted() {
    this.setupMarquee();

    this.loopInterval = setInterval(() => {
      this.loopCounter++;

      if (this.loop !== 0 && this.loopCounter === this.loop) {
        this.$emit('onComplete');
        clearInterval(this.loopInterval);
      }

      this.$emit('onLoopComplete');

      // Converting the duration into milliseconds here
    }, this.duration * 1000);
  },
  beforeDestroy() {
    clearInterval(this.loopInterval);
  },
  methods: {
    async ForcesUpdate() {
      await this.checkForClone();

      this.componentKey++;
    },

    async checkForClone() {
      if (this.vertical) {
        // pause the animation to prevent flickering
        this.verticalAnimationPause = true;
      }

      setInterval(() => {
        this.minWidth = '0%';
        this.minHeight = '0%';

        const marqueeContent = this.$refs.marqueeContent;
        const marqueeOverlayContainer = this.$refs.marqueeOverlayContainer;

        if (marqueeContent !== null && marqueeOverlayContainer !== null) {
          if (marqueeContent && marqueeOverlayContainer) {
            if (this.vertical && 'clientHeight' in marqueeContent && 'clientHeight' in this.marqueeOverlayContainer) {
              this.contentHeight = marqueeContent.clientHeight;
              this.containerHeight = marqueeOverlayContainer.clientHeight;

              const localCloneAmount = Math.ceil(this.containerHeight / this.contentHeight);

              this.cloneAmount = isFinite(localCloneAmount) ? localCloneAmount : 0;

              // resume the animation
              this.verticalAnimationPause = false;

              return this.cloneAmount;
            } else if (!this.vertical && 'clientWidth' in marqueeContent && 'clientWidth' in marqueeOverlayContainer) {
              this.contentWidth = marqueeContent.clientWidth;
              this.containerWidth = marqueeOverlayContainer.clientWidth;

              const localCloneAmount = Math.ceil(this.containerWidth / this.contentWidth);

              this.cloneAmount = isFinite(localCloneAmount) ? localCloneAmount : 0;

              return this.cloneAmount;
            } else {
              this.minWidth = '100%';
              this.minHeight = '100%';
              return 0;
            }
          } else {
            this.minWidth = '100%';
            this.minHeight = '100%';
            return 0;
          }
        } else {
          this.minWidth = '100%';
          this.minHeight = '100%';
          return 0;
        }
      }, 100);
    },

    hoverStarted() {
      if (this.pauseOnHover) {
        this.$emit('onPause');
      }
    },

    hoverEnded() {
      if (this.pauseOnHover) {
        this.$emit('onResume');
      }
    },

    mouseDown() {
      if (this.pauseOnClick) {
        this.$emit('onPause');
      }
    },

    mouseUp() {
      if (this.pauseOnClick) {
        this.$emit('onResume');
      }
    },

    async setupMarquee() {
      if (this.vertical) {
        this.minHeight = '100%';
        this.minWidth = 'auto';
      } else {
        this.minHeight = 'auto';
        this.minWidth = '100%';
      }

      // Deprecate the gradientWidth prop in favor of gradientLength
      if (this.gradient) {
        if (this.gradientWidth) {
          console.warn(
            'The `gradientWidth` prop has been deprecated and will be removed in a future release. Please use `gradientLength` instead.'
          );

          this.currentGradientLength = this.gradientWidth;
        } else if (this.gradientLength) {
          this.currentGradientLength = this.gradientLength;
        }
      }

      if (this.clone) {
        await this.checkForClone();
        this.ForcesUpdate();
        this.ready = true;
      } else {
        this.ready = true;
      }
    },
  },
};
</script>

<style>
.vue-marquee {
  position: relative;
  display: flex !important;
}

.vue-marquee.horizontal {
  flex-direction: row !important;
  width: 100%;
  height: max-content;
  overflow-x: hidden !important;
}

.vue-marquee.vertical {
  flex-direction: column !important;
  width: max-content;
  height: 100%;
  overflow-y: hidden !important;
}

.vue-marquee:hover div {
  animation-play-state: var(--pauseOnHover);
}

.vue-marquee:active div {
  animation-play-state: var(--pauseOnClick);
}

.vue-marquee > .marquee {
  z-index: 1;
  flex: 0 0 auto;
  min-width: var(--min-width);
  min-height: var(--min-height);
  animation: var(--orientation) var(--duration) linear var(--delay) var(--loops);
  animation-play-state: var(--pauseAnimation);
  animation-direction: var(--direction);
}

.vue-marquee.horizontal > .marquee {
  display: flex;
  flex-direction: row;
  align-items: center;
}

.vue-marquee.vertical > .marquee {
  display: flex;
  flex-direction: column;
  align-items: center;
}

@keyframes vue-marquee-scrollX {
  0% {
    transform: translateX(0%);
  }

  100% {
    transform: translateX(-100%);
  }
}

@keyframes vue-marquee-scrollY {
  0% {
    transform: translateY(0%);
  }

  100% {
    transform: translateY(-100%);
  }
}

.vue-marquee > .overlay {
  position: absolute;
  width: 100%;
  height: 100%;
}

.vue-marquee > .transparent-overlay {
  position: absolute;
  width: 100%;
  height: 100%;
}

.vue-marquee > .overlay::before,
.vue-marquee > .overlay::after {
  position: absolute;
  z-index: 2;
  content: '';
}

.vue-marquee.horizontal > .overlay::before,
.vue-marquee.horizontal > .overlay::after {
  width: var(--gradient-length);
  height: 100%;
  background: linear-gradient(to right, var(--gradient-color));
}

.vue-marquee.vertical > .overlay::before,
.vue-marquee.vertical > .overlay::after {
  width: 100%;
  height: var(--gradient-length);
  background: linear-gradient(to bottom, var(--gradient-color));
}

.vue-marquee.horizontal > .overlay::after {
  transform: rotateZ(180deg);
}

.vue-marquee.vertical > .overlay::after {
  transform: rotateZ(-180deg);
}

.vue-marquee > .overlay::before {
  top: 0;
  left: 0;
}

.vue-marquee.horizontal > .overlay::after {
  top: 0;
  right: 0;
}

.vue-marquee.vertical > .overlay::after {
  bottom: 0;
  left: 0;
}
</style>
